/**
 * file name:		SwapByteBuffer.java
 * copyright:		© 2009 Kingsoft
 * author:			TanMenglong
 */

package buffer;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * Random accessible byte array in both memory and disk.
 * @author TanMenglong
 *
 */
public class SwapByteArray1 {
	protected static final long DEFAULT_SWAP_SIZE = 2048L;
	protected static final int DEFAULT_MEM_SIZE = 256;
	protected static final int BUFF_SIZE = 1024;
	
	protected byte[] buff = null;		// buff in memory
	protected long swapSize = 0;		// swap file size
	protected int memSize = 0;			// size of memory
	protected long length = 0;		// total logical length
	protected File swap = null;
	protected RandomAccessFile swapAccessor = null;
	
	public SwapByteArray1(String swapFilePath) {
		this(DEFAULT_MEM_SIZE, DEFAULT_SWAP_SIZE, swapFilePath);
	}
	
	public SwapByteArray1(int memSize, long swapSize, String swapFilePath) {

		this.memSize = memSize;
		this.swap = new File(swapFilePath);
		this.swapSize = swapSize;
		this.length = memSize + swapSize;
	}
	
	public void init() throws IOException {

		try {
			this.buff = new byte[this.memSize];
		} catch (OutOfMemoryError e) {
			throw new OutOfMemoryError("Java heap space");
		}
		
		try {
			initSwap();
		} catch (IOException e) {
			buff = null;
			if (swap.exists()) {
				swap.delete();
			}
			throw e;
		}
		
		this.swapAccessor = new RandomAccessFile(swap, "rw");
	}
	
	/**
	 * Close array and the underlying stream.
	 * @throws IOException
	 */
	public void close() throws IOException {
		if (swapAccessor != null) {
			swapAccessor.close();
		}
	}
	
	/**
	 * The total logical length.
	 * @return the total logical length.
	 */
	public long length() {
		return this.memSize + this.swapSize;
	}
	
	protected void initSwap() throws IOException {
		swap.createNewFile();
		FileOutputStream fos = new FileOutputStream(swap);
		BufferedOutputStream bos = new BufferedOutputStream(fos);

		byte[] emptyData = new byte[BUFF_SIZE];
		try {
			for (int i = 0; i < swapSize / BUFF_SIZE; ++i) {
				bos.write(emptyData);
			}
			for (int i = 0; i < swapSize % BUFF_SIZE; ++i) {
				bos.write(0x00);
			}
		} catch (IOException e) {
			throw e;
		} finally {
			try {
				bos.close();
			} catch (IOException e) {
				throw e;
			}
		}
	}
	
	/**
	 * Reads the next byte of data from the array.
	 * @param pos
	 * @return
	 * @throws IndexOutOfBoundsException
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public int read(long pos) throws IndexOutOfBoundsException, IOException, IllegalArgumentException {
		if (pos < 0) {
			throw new IllegalArgumentException();
		}

		if (pos < 0 || pos >= this.length) {
			throw new IndexOutOfBoundsException();
		}
		
		int result;
		
		if (pos < this.memSize) {
			result = buff[(int) pos];
		} else {
			synchronized (swapAccessor) {
				this.swapAccessor.seek(pos - this.memSize);
				result = this.swapAccessor.read();
			}
		}
		
		return result;
	}
	
	
	public void read(byte[] b, int pos, int len) 
	throws IndexOutOfBoundsException, IOException, IllegalArgumentException {
		if (null == b || pos < 0 || len < 0) {
			throw new IllegalArgumentException();
		}
		
		if (pos < 0 || pos >= this.length || b.length < len || pos + b.length > this.length) {
			throw new IndexOutOfBoundsException();
		}
		
		for (int i = 0; i < len; ++i) {
			b[i] = (byte) read(pos + i);
		}
	}
	
	public void read(byte[] b, int pos) 
	throws IndexOutOfBoundsException, IOException, IllegalArgumentException {
		read(b, pos, b.length);
	}

	/**
	 * Writes one byte to the array. This method will block until all the bytes are written.
	 * @param data
	 * @param pos
	 * @throws IndexOutOfBoundsException
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public void write(int data, long pos) throws IndexOutOfBoundsException, IOException ,IllegalArgumentException {
		if (pos < 0) {
			throw new IllegalArgumentException();
		}

		if (pos < 0 || pos >= this.length) {
			throw new IndexOutOfBoundsException();
		}

		if (pos < this.memSize) {
			buff[(int)pos] = (byte) data;
		} else {
			synchronized (swapAccessor) {
				this.swapAccessor.seek(pos - this.memSize);
				this.swapAccessor.write(data);
			}
		}
	}
	
	/**
	 * Writes array of bytes to the array. This method will block until all the bytes are written.
	 * @param b
	 * @param pos
	 * @param len
	 * @throws IndexOutOfBoundsException
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public void write(byte[] b, long pos, int len) 
	throws IndexOutOfBoundsException, IOException, IllegalArgumentException {
		if (null == b || pos < 0 || len < 0) {
			throw new IllegalArgumentException();
		}
		
		if (pos < 0 || pos >= this.length || b.length < len || pos + b.length > this.length) {
			throw new IndexOutOfBoundsException();
		}

		for (int i = 0; i < len; ++i) {
			write(b[i], pos + i);
		}
	}
	
	/**
	 * Writes array of bytes to the array. This method will block until all the bytes are written.
	 * @param b
	 * @param pos
	 * @throws IndexOutOfBoundsException
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public void write(byte[] b, long pos) 
	throws IndexOutOfBoundsException, IOException, IllegalArgumentException {
		write(b, pos, b.length);
	}
}
